package org.teamh.artificialIntelligence;

import java.util.ArrayList;
import java.util.HashMap;

import org.teamh.logic.datamodel.Creep;
import org.teamh.logic.datamodel.Tower;
import org.teamh.logic.datamodel.User;
import org.teamh.logic.datamodel.Waypoint;

public class Simulator {
	private static final int CLOSEST = 'L' * Character.MAX_VALUE + 'O';
	private static final int FARTHEST = 'A' * Character.MAX_VALUE + 'R';
	private static final int FASTEST = 'A' * Character.MAX_VALUE + 'S';
	private static final int STRONGEST = 'T' * Character.MAX_VALUE + 'R';
	private static final int WEAKEST = 'E' * Character.MAX_VALUE + 'A';
	
	private static final float COOLDOWN_MULTIPLICATOR = 1.2f;
	
	private static HashMap<String, Waypoint> oldList = new HashMap<String, Waypoint>();
	private static HashMap<User, ArrayList<Tower>> oldTowerList = new HashMap<User, ArrayList<Tower>>();
	private static ArrayList<Creep> currentCreepsToShotOn = new ArrayList<Creep>();
	public static ArrayList<Creep> currentCreeps = new ArrayList<Creep>();
	private static ArrayList<Tower> currentTowers = new ArrayList<Tower>();
	
	private static long lastCheck;
	
	private Simulator() {}
	
	static int i = 0;
	
	public static void simulate(User user) {
		int timeWait = 120;
		
		fillCreepList(user, currentCreeps);
		fillTowerList(user, currentTowers);
		
		prepareForSimulation();
		
		long timeStamp = System.currentTimeMillis();
		
		while(isSimulating(currentCreeps, user)) {
			System.out.println("is simulating");
			updateCreeps(user, currentCreeps, timeWait);
			System.out.println("####1");
			updateTowerShots(currentTowers, timeStamp, user, currentCreeps, timeWait);
			System.out.println("####2");
			timeStamp += timeWait;
			
		}
	}
	
	private static void prepareForSimulation() {
		for(Creep c: currentCreeps) {
			while(c.getCurrentWaypoint() == null) {
				try {
					Thread.sleep(20);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//System.out.println("sleep");
			}
			c.setCurrentWaypointForSimulation(c.getCurrentWaypoint());
			c.setPosXForSimulation(c.getWorldX());
			c.setPosZForSimulation(c.getWorldZ());
			while(c.getSpeed() == 0) {
				try {
					Thread.sleep(20);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//System.out.println("sleep");
			}
			c.setSpeedForSimulation(c.getSpeed());
			c.setSlowedForSimulation(c.isSlowed());
			c.setSlowTimeForSimulation(c.getSlowTime());
			c.setWalkedDistanceForSimulation(c.getWalkedDistance());
			c.setHealthForSimulation(c.getHealth());
		}
		
		for(Tower t: currentTowers){
			t.setLastShotForSimulation(t.getLastShot());
			
			if(!t.isChangedBySimulation()) {
				while(t.getTowerDescription() == null || t.getStratetgy() == null || t.getLevel() == 0) {
					AIInterfaceManager.waits(20);
					//System.out.println("sleep for strategy");
				}
				t.setStrategyForSimulation(t.getStratetgy());
				t.setLevelForSimulation(t.getLevel());
				t.setTowerDescriptionForSimulation(t.getTowerDescription());
			}
			
			t.setCooldownForSimulation(t.getTowerDescriptionForSimulation().getCoolDownMax() * COOLDOWN_MULTIPLICATOR);
			
			t.setChangedBySimulation(false);
		}
	}
	
	private static void fillCreepList(User user, ArrayList<Creep> creeps) {
		creeps.clear();
		for(Waypoint w: user.getUserAssets().getTile().getMap().getWaypoints().values()) {
			for(Creep c: w.getCell().getCreeps().values()) {
				if(!creeps.contains(c)) {
					creeps.add(c);
				}
			}
		}
	}
	
	private static void fillTowerList(User user, ArrayList<Tower> towers) {
		towers.clear();
		for(Tower t: user.getUserAssets().getTowers().values()) {
			towers.add(t);
		}
	}
	
	private static void updateCreeps(User user, ArrayList<Creep> creeps, float timeElapsed) {
		for(Creep c: creeps) {
			updateCreep(user, c, timeElapsed);
		}
	}
	
	private static void updateCreep(User user, Creep c, float timeElapsed) {
		c.addToWalkedDistanceForSimulation(timeElapsed);
		
		float curDistance = (float)(c.getCurrentWaypointForSimulation().getWayToPoint() * c.getWalkedDistanceForSimulation() * c.getSpeedForSimulation() / 1000);
		
		if(c.getWalkedDistanceForSimulation() > 1000 / c.getSpeedForSimulation()) {
			if(c.getCurrentWaypointForSimulation() != user.getUserAssets().getTile().getMap().getExitWaypoint()) {
				c.setCurrentWaypointForSimulation(c.getCurrentWaypointForSimulation().getNext());
			}
		} else {
			c.setPosXForSimulation(c.getCurrentWaypointForSimulation().getCell().getX() + c.getVectorForSimulation()[0] * curDistance);
			c.setPosZForSimulation(c.getCurrentWaypointForSimulation().getCell().getY() + c.getVectorForSimulation()[1] * curDistance);
		}
		
		if(c.isSlowedForSimulation()) {
			c.setSlowTimeForSimulation((int)(c.getSlowTimeForSimulation() - timeElapsed));
			
			if(c.getSlowTimeForSimulation() < 0) {
				c.setSlowTimeForSimulation(0);
				c.setSlowedForSimulation(false);
				c.setSpeedForSimulation(c.getCreepDescription().getSpeedMax());
			}
		}
	}
	
	private static void updateTowerShots(ArrayList<Tower> towers, long timeStamp, User user, ArrayList<Creep> creeps, float timeElapsed) {
		for(Tower t: towers) {
			updateTower(timeStamp, user, t, creeps, timeElapsed);
		}
	}
	
	private static void updateTower(long timeStamp, User user, Tower t, ArrayList<Creep> creeps, float timeElapsed) {
		if(t.canFireForSimulation(timeStamp)) {
			for(Creep c: creeps) {
				if(c.getHealthForSimulation() > 0 
						&& c.getCurrentWaypointForSimulation() != user.getUserAssets().getTile().getMap().getExitWaypoint() 
						&& distance(c, t) < t.getTowerDescriptionForSimulation().getRange() * t.getTowerDescriptionForSimulation().getRange()) {
					currentCreepsToShotOn.add(c);
				}
			}
			
			//System.out.println("####4");
			
			if(currentCreepsToShotOn.size() > 0) {
				Creep fireOn = null;
				switch(t.getStrategyForSimulation().charAt(1) * Character.MAX_VALUE + t.getStrategyForSimulation().charAt(2)) {
				case CLOSEST: fireOn = getClosest(currentCreepsToShotOn, t); break;
				case FARTHEST: fireOn = getFarthest(currentCreepsToShotOn); break;
				case FASTEST: fireOn = getFastest(currentCreepsToShotOn); break;
				case STRONGEST: fireOn = getStrongest(currentCreepsToShotOn); break;
				case WEAKEST: fireOn = getWeakest(currentCreepsToShotOn); break;
				default: //System.out.println("curious error"); return;
				}
				
				//System.out.println("####5");
				
				fireOn.setHealthForSimulation(fireOn.getHealthForSimulation() - t.getTowerDescriptionForSimulation().getDamage());
				
				if(t.getTowerDescriptionForSimulation().getSlowRate() > 0 && !fireOn.isSlowedForSimulation()) {
					fireOn.setSpeedForSimulation(fireOn.getCreepDescription().getSpeedMax() * t.getTowerDescriptionForSimulation().getSlowRate());
					fireOn.setSlowTimeForSimulation(t.getTowerDescriptionForSimulation().getSlowTime());
					fireOn.setSlowedForSimulation(true);
				}
				
				//System.out.println("####6");
				
				if(t.getTowerDescriptionForSimulation().getSplashRadius() > 0) {
					if(t.getTowerDescriptionForSimulation().getSlowRate() > 0) {
						//System.out.println("####7");
						for(Creep c: creeps) {
							float dist = (float)distance(fireOn, c);
							if(dist < t.getTowerDescriptionForSimulation().getSplashRadius() && 
									c.getHealthForSimulation() > 0 
									&& c.getCurrentWaypointForSimulation() != user.getUserAssets().getTile().getMap().getExitWaypoint()) {
								c.setHealthForSimulation(c.getHealthForSimulation() + (int)(t.getTowerDescriptionForSimulation().getSplashDamageReduce() / t.getTowerDescriptionForSimulation().getSplashRadius() * dist - 1) * t.getTowerDescriptionForSimulation().getDamage());
							}
							
							if(!c.isSlowedForSimulation()) {
								c.setSpeedForSimulation(c.getCreepDescription().getSpeedMax() * t.getTowerDescriptionForSimulation().getSlowRate());
								c.setSlowTimeForSimulation(t.getTowerDescriptionForSimulation().getSlowTime());
								c.setSlowedForSimulation(true);
							}
						}
						
						//System.out.println("####8");
					} else {
						//System.out.println("####9");
						for(Creep c: creeps) {
							float dist = (float)distance(fireOn, c);
							if(dist < t.getTowerDescriptionForSimulation().getSplashRadius() && c.getHealthForSimulation() > 0 
									&& c.getCurrentWaypointForSimulation() != user.getUserAssets().getTile().getMap().getExitWaypoint()) {
								c.setHealthForSimulation(c.getHealthForSimulation() + (int)(t.getTowerDescriptionForSimulation().getSplashDamageReduce() / t.getTowerDescriptionForSimulation().getSplashRadius() * dist - 1) * t.getTowerDescriptionForSimulation().getDamage());
							}
						}
						//System.out.println("#####10");
					}

				}
				
				t.setLastShotForSimulation(timeStamp);
				currentCreepsToShotOn.clear();
				
				//System.out.println("#####11");
			}
		}
	}
	
	private static final Creep getClosest(ArrayList<Creep> creeps, Tower t) {
		Creep min = creeps.get(0);
		
		for(Creep c: creeps) {
			if(distance(min, t) > distance(c, t)) {
				min = c;
			}
		}
		
		return min;
	}
	
	private static final Creep getFarthest(ArrayList<Creep> creeps) {
		Creep farthest = creeps.get(0);
		
		for(Creep c: creeps) {
			if(farthest.getCurrentWaypointForSimulation().getIndex() < c.getCurrentWaypointForSimulation().getIndex()) {
				farthest = c;
			} else if(farthest.getCurrentWaypointForSimulation().getIndex() == c.getCurrentWaypointForSimulation().getIndex() 
					&& farthest.getWalkedDistanceForSimulation() < c.getWalkedDistanceForSimulation()) {
				farthest = c;
			}
		}
		
		return farthest;
	}
	
	private static final Creep getFastest(ArrayList<Creep> creeps) {
		Creep fastest = creeps.get(0);
		
		for(Creep c: creeps) {
			if(fastest.getSpeedForSimulation() < c.getSpeedForSimulation()) {
				fastest = c;
			}
		}
		
		return fastest;
	}
	
	private static final Creep getStrongest(ArrayList<Creep> creeps) {
		Creep strongest = creeps.get(0);
		
		for(Creep c: creeps) {
			if(strongest.getHealthForSimulation() < c.getHealthForSimulation()) {
				strongest = c;
			}
		}
		
		return strongest;
	}
	
	private static final Creep getWeakest(ArrayList<Creep> creeps) {
		Creep weakest = creeps.get(0);
		
		for(Creep c: creeps) {
			if(weakest.getHealthForSimulation() > c.getHealthForSimulation()) {
				weakest = c;
			}
		}
		
		return weakest;
	}
	
	private static double distance(Creep c1, Creep c2) {
		return Math.sqrt((c1.getPosXForSimulation() - c2.getPosXForSimulation()) * (c1.getPosXForSimulation() - c2.getPosXForSimulation()) 
			+ (c1.getPosZForSimulation() - c2.getPosZForSimulation()) * (c1.getPosZForSimulation() - c2.getPosZForSimulation()));
	}
	
	private static double distance(Creep c, Tower t) {
		try{
			return  (c.getPosXForSimulation() - t.getCell().getX()) * (c.getPosXForSimulation() - t.getCell().getX()) 
					+ (c.getPosZForSimulation() - t.getCell().getY()) * (c.getPosZForSimulation() - t.getCell().getY());
		} catch(NullPointerException e) {
			e.printStackTrace();
			//System.out.println("creep: " + c);
			//System.out.println("tower: " + t);
			//System.out.println("cell: " + t.getCell());
		}
		
		return Double.MAX_VALUE;
	}
	
	private static boolean isSimulating(ArrayList<Creep> creeps, User user) {
		for(Creep c: creeps) {
			if(c.getCurrentWaypointForSimulation() != user.getUserAssets().getTile().getMap().getExitWaypoint()) {
				//System.out.println("current waypoint: " + c.getCurrentWaypointForSimulation());
				//System.out.println("next waypoint: " + c.getCurrentWaypointForSimulation().getNext());
			}
			
			if(c.getHealthForSimulation() > 0 && c.getCurrentWaypointForSimulation() != user.getUserAssets().getTile().getMap().getExitWaypoint()) {
				return true;
			}
		}
		
		return false;
	}
	
	public static boolean creepListChanged(User user) {
		if(Math.abs(System.currentTimeMillis() - lastCheck) < 1000) {
			lastCheck = System.currentTimeMillis();
			return true;
		}
		
		ArrayList<Creep> creeps = new ArrayList<Creep>();
		fillCreepList(user, creeps);
		
		for(Creep c: creeps) {
			if(!oldList.containsKey(c.getID()) || c.isNewInField()){
				oldList.clear();
				for(Creep c2: creeps) {
					oldList.put(c2.getID(), c2.getCurrentWaypoint());
				}
				
				return true;
			}
		}

		return false;
	}
	
	public static boolean towerListChanged(User user) {
		ArrayList<Tower> towers = new ArrayList<Tower>();
		fillTowerList(user, towers);
		
		if(oldTowerList.get(user) == null) {
			oldTowerList.put(user, new ArrayList<Tower>());
		}
		
		for(Tower t1: towers) {
			if(!oldTowerList.get(user).contains(t1)) {
				oldTowerList.put(user, towers);
				return true;
			}
		}
		
		return false;
	}
}
